#import "PAThumbWheelCellDrawing.h"

/******************************************************************************
	PAThumbWheelCellDrawing
	
This file handles all of the drawing for the PAThumbWheelCell. Don't try to make sense of any of this.

Copyright 1992, Jeff Martin. (jmartin@next.com 415-780-3833)
******************************************************************************/
#define EQUAL(a,b) (ABS((a)-(b))<0.00001)
#define NOTEQUAL(a,b) (ABS((a)-(b))>0.0001)
#define ISBETWEEN(x,a,b) (((x)>=(a))&&((x)<=(b)))
// A mod function for floating values
#define MOD(x,y) ((x) - (y)*(int)((float)(x)/(y)))
#define CLAMP_WITH_WRAP(a,x,y)  											\
( ((a) < (x)) ? ((y) - MOD(((x)-(a)),((y)-(x)))) : 						\
( ((a) > (y)) ? ((x) + MOD(((a)-(y)),((y)-(x)))) : (a) ) )
#define EVEN(x) (!(((int)(x))%2))


@implementation PAThumbWheelCell(Drawing)

- drawSelf:(const NXRect *)theFrame inView:view
{
	NXRect frame = *theFrame;
	float *pnts;						// Used for user path of dashes
	char *ops;							// Used for user path of dashes
	int pntCount, opCount;				// Used for user path of dashes
	float bbox[4] = { NX_X(theFrame), NX_Y(theFrame),
		NX_MAXX(theFrame), NX_MAXY(theFrame) };
	
	// Inset by two to allow for bezeled border
	NXInsetRect(&frame,2,2);

	// Draw the background
	if([self isLinear]) {
		NXDrawGrayBezel(theFrame, theFrame);
		NXSetColor(color);
		NXRectFill(&frame);
	}
	else { // if([self isRadial])
		NXSize imageSize;
		[image getSize:&imageSize];
		if(NOTEQUAL(imageSize.width,NX_WIDTH(theFrame)) ||
			NOTEQUAL(imageSize.height,NX_HEIGHT(theFrame)))
			[self generateImage:theFrame];
		[image composite:NX_COPY toPoint:&theFrame->origin];
	}
	
	// Get the userpath for the dashes
	[self getDashesForFrame:&frame :&pnts :&pntCount :&ops :&opCount];

	// Draw dashes once for white part of groove
	if([self isHorizontal]) PStranslate(1,0); else PStranslate(0,-1);

	// Draw linear white dashes
	if([self isLinear]) {
		NXSetColor(PAScaleRGBColor(color, 1.5));
		DPSDoUserPath(pnts, pntCount, dps_float, ops, opCount, bbox, 
			dps_ustroke);
	}
	
	// Break up radial white dashes to fade a little bit at ends
	else {
		int i = 0, j = 0, d = [self isHorizontal]? 0 : 1;
		float oneQuarterOfX = [self isHorizontal]? (NX_X(&frame) + 
			NX_WIDTH(&frame)/4) : (NX_Y(&frame) + NX_HEIGHT(&frame)/4);
		float threeQuartersOfX =[self isHorizontal]? (NX_X(&frame) + 
			3*NX_WIDTH(&frame)/4) : (NX_Y(&frame) + 3*NX_HEIGHT(&frame)/4);

		NXSetColor(color);
		while((i < pntCount) && (pnts[i+d] < oneQuarterOfX)) i += 4; j = i;
		if(i>0) DPSDoUserPath(pnts, i, dps_float, ops, i/2, bbox, dps_ustroke);
	
		NXSetColor(PAScaleRGBColor(color, 1.5));
		while((j < pntCount) && (pnts[j+d] < threeQuartersOfX)) j+=4;
		if(j>i) DPSDoUserPath(&pnts[i], j-i, dps_float, &ops[i/2], j/2-i/2,
			bbox, dps_ustroke);
	
		NXSetColor(color);
		if(pntCount>j) DPSDoUserPath(&pnts[j], pntCount-j, dps_float, 
			&ops[j/2], opCount-j/2, bbox, dps_ustroke);
	}
	
	if([self isHorizontal]) PStranslate(-1,0); else PStranslate(0,1);

	// Draw again for dark part of groove
	if([self isLinear]) NXSetColor(PAScaleRGBColor(color, .5)); 
	else NXSetColor(NX_COLORBLACK);
	DPSDoUserPath(pnts, pntCount, dps_float, ops, opCount, bbox, dps_ustroke);
	
	// If disabled then dim ThumbWheel out
	if(![self isEnabled]) {
		NXSetColor(NX_COLORWHITE); PSsetalpha(.5);
		PScompositerect(NX_X(&frame), NX_Y(&frame), NX_WIDTH(&frame),
			NX_HEIGHT(&frame), NX_SOVER);
	}
	
	// Free user path variables
	free(pnts); free(ops);
	return self;
}

- getDashesForFrame:(const NXRect *)frame :(float **)PNTS :(int *)PNTCOUNT :(char **)OPS :(int *)OPCOUNT
{
	// Get dashInterval and shift
	int dashInt = [self dashInterval];
	int shift = [self shift:frame];

	// Calculate how many dashes there will be and alloc space for pnts and ops
	int dashCount = 2 + ([self isRadial] ? 180/dashInt : 
		([self isVertical] ? NX_HEIGHT(frame) : NX_WIDTH(frame)) / dashInt);
	float *pnts = malloc(sizeof(float)*dashCount*4); // (moveto+lineto)*(x+y)=4
	char *ops 	= malloc(sizeof(char)*dashCount*2);  // (moveto+lineto) = 2
	int i=0, j=0;
	
	// Calculate dash sizes
	int dashBase 	= [self isVertical] ? NX_X(frame) : NX_Y(frame);
	int dashHeight	= [self isVertical] ? NX_WIDTH(frame) : NX_HEIGHT(frame);
	int dashMinTop 	= dashBase + dashHeight*.25;
	int dashMajTop 	= dashBase + dashHeight*.5;
	int dashTop		= dashBase + dashHeight;
	
	float base		=	[self isVertical] ? NX_Y(frame) : NX_X(frame);
	float width		=	[self isVertical]? NX_HEIGHT(frame) : NX_WIDTH(frame);
	float halfWidth	=	width/2;
	float mid		=	base + halfWidth;
	float top		=	base + width;

	float mainDash;
	float x;
	
	// Calculate whether first dash is a major one. 
	BOOL isMajor	= (shift>=0)? EVEN(shift/dashInt) : !EVEN(shift/dashInt);
		
	// Calculate Linear dashes
	if([self isLinear]) {
		// Set Main dash
		mainDash = base + shift;

		// Calculate starting point and set the dashes
		x = base+CLAMP_WITH_WRAP(shift,0,dashInt)%((shift>=0)? dashInt:999999);
		if([self isVertical]) while(x<top) {
			pnts[i++] = dashBase; pnts[i++] = x;
			pnts[i++] = isMajor ? dashMajTop : dashMinTop;
			if(EQUAL(x, mainDash)&&[self showMainDash]) pnts[i-1] = dashTop;
			pnts[i++] = x;
			x += dashInt; isMajor = !isMajor;
		}
		else while(x<top) {
			pnts[i++] = x; pnts[i++] = dashBase;
			pnts[i++] = x; pnts[i++] = isMajor ? dashMajTop : dashMinTop;
			if(EQUAL(x, mainDash)&&[self showMainDash]) pnts[i-1] = dashTop;
			x += dashInt; isMajor = !isMajor;
		}
	}

	// Calculate Radial Dashes
	else {
		// This is used to convert the degrees of a dash to a location(in pnts)
		float linDash = 0;
		
		// Inset dash size for beveled edges
		dashBase++; dashTop--;
		
		// Calc Main dash if we show it and it is in sight
		mainDash = mid - IntCos(shift)*halfWidth;

		// Calculate the starting point and set the dashes
		x = CLAMP_WITH_WRAP(shift, 0, dashInt)%((shift>=0)? dashInt:999999);
		if([self isVertical]) while(x<180) {
			linDash = mid - IntCos(x)*halfWidth;
			pnts[i++] = dashBase; pnts[i++] = linDash;
			pnts[i++] = isMajor ? dashMajTop : dashMinTop;
			// Check to see if this is a valid main dash
			if(isMajor && EQUAL(linDash,mainDash) && [self showMainDash] &&
				ISBETWEEN(shift, 0, 180)) pnts[i-1]=dashTop;
			pnts[i++] = linDash;
			x += dashInt; isMajor = !isMajor;
		}
		else while(x<180) {
			linDash = mid - IntCos(x)*halfWidth;
			pnts[i++] = linDash; pnts[i++] = dashBase;
			pnts[i++] = linDash; pnts[i++] = isMajor ? dashMajTop : dashMinTop;
			// Check to see if this is a valid main dash
			if(isMajor && EQUAL(linDash,mainDash) && [self showMainDash] &&
				ISBETWEEN(shift, 0, 180)) pnts[i-1]=dashTop;
			x += dashInt; isMajor = !isMajor;
		}
	}
		
	// fill the ops array with dps_moveto and dps_lineto
	while(j<i/2) { ops[j++] = dps_moveto; ops[j++] = dps_lineto; }
	 
	// Set the passed in pointers to the arrays and the counts and return
	*PNTS = pnts; *OPS = ops; *PNTCOUNT = i; *OPCOUNT = j;
	return self;
}

- generateImage:(const NXRect *)theFrame
{
	float circleHeight;
	float x, rad, rad2, mid;
	NXRect frame = *theFrame;

	if(!image) image = [[NXImage alloc] init];
	[image setSize:&frame.size];
	[image lockFocus];
	NXDrawGrayBezel(&frame,&frame);
	NXInsetRect(&frame,2,2);
	PSsetlinewidth(0.0); 

	// Draw horizonal version
	if(direction == DIRECTION_HORIZONTAL) {
		rad = NX_WIDTH(&frame)/2.0; rad2 = rad*rad;
		mid = NX_MIDX(&frame);

		for(x = NX_X(&frame); x <= NX_MIDX(&frame); x++) {
			float topx = NX_MAXX(&frame) - (x - NX_X(&frame));
			circleHeight = sqrt(rad2 - (mid-x)*(mid-x))/rad;

			PSnewpath();
			NXSetColor(PAScaleRGBColor(color, .5*circleHeight));
			PSmoveto(x, NX_Y(&frame));		PSlineto(x, NX_Y(&frame) + 2);
			PSmoveto(topx, NX_Y(&frame));	PSlineto(topx, NX_Y(&frame) + 2);
			PSstroke();

			PSnewpath();
			NXSetColor(PAScaleRGBColor(color, circleHeight));
			PSmoveto(x, NX_Y(&frame)+2);	PSlineto(x, NX_MAXY(&frame) - 1);
			PSmoveto(topx, NX_Y(&frame)+2);PSlineto(topx,NX_MAXY(&frame) -1);
			PSstroke();

			PSnewpath();
			NXSetColor(PAScaleRGBColor(color,1.5*circleHeight));
			PSmoveto(x, NX_MAXY(&frame)-1);	PSlineto(x, NX_MAXY(&frame));
			PSmoveto(topx, NX_MAXY(&frame)-1);PSlineto(topx, NX_MAXY(&frame));
			PSstroke();
		}
	}
	else {
		rad = NX_HEIGHT(&frame)/2.0; rad2 = rad*rad;
		mid = NX_MIDY(&frame);
		for(x = NX_Y(&frame); x <= NX_MIDY(&frame); x++) {
			float topx = NX_MAXY(&frame) - (x - NX_Y(&frame));
			circleHeight = sqrt(rad2 - (mid-x)*(mid-x))/rad;

			PSnewpath();
			NXSetColor(PAScaleRGBColor(color, 1.5*circleHeight));
			PSmoveto(NX_X(&frame), x);		PSlineto(NX_X(&frame) + 2, x);
			PSmoveto(NX_X(&frame), topx);	PSlineto(NX_X(&frame) + 2, topx);
			PSstroke();

			PSnewpath();
			NXSetColor(PAScaleRGBColor(color, circleHeight));
			PSmoveto(NX_X(&frame)+2, x);	PSlineto(NX_MAXX(&frame) - 1, x);
			PSmoveto(NX_X(&frame)+2, topx);	PSlineto(NX_MAXX(&frame) - 1,topx);
			PSstroke();

			PSnewpath();
			NXSetColor(PAScaleRGBColor(color, .5*circleHeight));
			PSmoveto(NX_MAXX(&frame)-1, x);	PSlineto(NX_MAXX(&frame), x);
			PSmoveto(NX_MAXX(&frame)-1, topx);PSlineto(NX_MAXX(&frame), topx);
			PSstroke();
		}
	}

	[image unlockFocus];
	return self;
}

- (const char *)getInspectorClassName { return "PAThumbWheelInspector"; }

@end

NXColor PAScaleRGBColor(NXColor c, float scale)
{ return NXConvertRGBToColor(NXRedComponent(c)*scale, NXGreenComponent(c)*
scale, NXBlueComponent(c)*scale); }

float _IntSin[91] ={0, 0.017452406, 0.034899497, 0.052335956, 0.069756474, 
  0.087155743, 0.10452846, 0.12186934, 0.1391731, 
  0.15643447, 0.17364818, 0.190809, 0.20791169, 
  0.22495105, 0.2419219, 0.25881905, 0.27563736, 
  0.2923717, 0.30901699, 0.32556815, 0.34202014, 
  0.35836795, 0.37460659, 0.39073113, 0.40673664, 
  0.42261826, 0.43837115, 0.4539905, 0.46947156, 
  0.48480962, 0.5, 0.51503807, 0.52991926, 0.54463904, 
  0.5591929, 0.57357644, 0.58778525, 0.60181502, 
  0.61566148, 0.62932039, 0.64278761, 0.65605903, 
  0.66913061, 0.68199836, 0.69465837, 0.70710678, 
  0.7193398, 0.7313537, 0.74314483, 0.75470958, 
  0.76604444, 0.77714596, 0.78801075, 0.79863551, 
  0.80901699, 0.81915204, 0.82903757, 0.83867057, 
  0.8480481, 0.8571673, 0.8660254, 0.87461971, 
  0.88294759, 0.89100652, 0.89879405, 0.90630779, 
  0.91354546, 0.92050485, 0.92718385, 0.93358043, 
  0.93969262, 0.94551858, 0.95105652, 0.95630476, 
  0.9612617, 0.96592583, 0.97029573, 0.97437006, 
  0.9781476, 0.98162718, 0.98480775, 0.98768834, 
  0.99026807, 0.99254615, 0.9945219, 0.9961947, 
  0.99756405, 0.99862953, 0.99939083, 0.9998477, 1.};

float IntSin(int x)
{
	if(x < 0)   return -IntSin(-x);
	if(x > 180) return -IntSin(x%180);
	if(x > 90)  return  _IntSin[180-x];
	return _IntSin[x];
}
